﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ACSR.Core.Processes
{
    public class MethodDocumentationAttribute : Attribute
    {
        public MethodDocumentationAttribute(string description)
        {
            this.Description = description;
        }

        public string Description { get; set; }
    }

    public class CommandAttribute : Attribute
    {
    }

    public abstract class CommandLineControllerBase : ICommandController
    {
        private  ICommandLinePrinter _Printer;

        public CommandLineControllerBase (ICommandLinePrinter printer)
	    {
            this._Printer = printer;    
	    }
        public ICommandLinePrinter Printer
        {
            get { return _Printer; }
        }

        public abstract bool CanExecute(MethodInfo method);
    }

    public class DefaultCommandLineController : CommandLineControllerBase
    {
        public DefaultCommandLineController(ICommandLinePrinter printer) : base(printer)
        {
        }
        public override bool CanExecute(MethodInfo method)
        {
            return true;
        }
    }

    public class AttributedCommandLineController : CommandLineControllerBase
    {
        public AttributedCommandLineController(ICommandLinePrinter printer)
            : base(printer)
        {
        }

        #region ICommandController Members

        public override bool CanExecute(MethodInfo method)
        {
            return method.GetCustomAttributes(typeof(CommandAttribute), false).Any();
        }

        #endregion
    }

    public interface ICommandLinePrinter
    {
        void WriteMessage(string message);
        void WriteWarning(string message);
        void WriteError(string error);
    }

    public class DefaultCommandLinePrinter : ICommandLinePrinter
    {

        #region ICommandLinePrinter Members

        public void WriteMessage(string message)
        {
            Console.ForegroundColor = ConsoleColor.Gray;
            Console.WriteLine(message);
        }

        public void WriteWarning(string message)
        {
            Console.ForegroundColor = ConsoleColor.DarkYellow;
            Console.WriteLine(message);
            Console.ForegroundColor = ConsoleColor.Gray;
        }

        public void WriteError(string error)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(error);
            Console.ForegroundColor = ConsoleColor.Gray;
        }

        #endregion
    }

    public class MethodPrinter
    {
        private ICommandController _Controller;
        public MethodPrinter(ICommandController controller)
        {
            this._Controller = controller;
        }

        void PrintParameters(MethodInfo method)
        {
            int index = 0;
            foreach (var parameter in method.GetParameters())
            {
                Console.ForegroundColor = ConsoleColor.DarkGray;
                if (index > 0)
                    Console.Write(", ");
                Console.Write(parameter.ParameterType.Name);
                Console.Write(" ");
                Console.ForegroundColor = ConsoleColor.Magenta;
                Console.Write(parameter.Name);
                if (parameter.IsOptional)
                {
                    Console.Write(" = ");
                    Console.Write(parameter.DefaultValue == null ? "<null>" : parameter.DefaultValue.ToString());
                }
                index++;
            }
        }

        public void Print()
        {
            foreach (var method in this._Controller.GetType().GetMethods().OrderBy(methi => methi.Name))
            {
                if (!this._Controller.CanExecute(method))
                {
                    continue;
                }
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write(method.Name + "  ");
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.WriteLine();
                Console.Write("(");
                PrintParameters(method);
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.WriteLine(")");
                Console.ForegroundColor = ConsoleColor.Gray;

                Console.WriteLine("Example: " + CommandLineApplication.GenerateMethodExample(method));

                foreach (MethodDocumentationAttribute attribute in method.GetCustomAttributes(typeof(MethodDocumentationAttribute), false))
                {
                    Console.ForegroundColor = ConsoleColor.DarkGray;
                    Console.WriteLine(attribute.Description);
                }
                Console.WriteLine();

            }
        }
    }

    public static class CommandLineApplication
    {

        public static string GenerateMethodExample(MethodInfo method)
        {
            var sb = new StringBuilder();
            foreach (var parameter in method.GetParameters())
            {
                if (sb.Length > 0)
                    sb.Append(" ");
                sb.Append("-");
                // sb.Append(parameter.ParameterType.Name + " ");
                sb.Append(parameter.Name);
                if (parameter.IsOptional)
                {
                    sb.Append(" \"" + parameter.DefaultValue + "\"");
                }
                else
                {
                    sb.Append(" \"<" + parameter.ParameterType.Name + ">\"");
                }
            }
            // return "(" + sb.ToString() + ")";
            return string.Format("{0} {1} {2}", Path.GetFileName(Assembly.GetEntryAssembly().Location), method.Name, sb.ToString());
        }

        public static void Run(ICommandController controller)
        {
            try
            {
                var cmd = new CmdLineHelper(true);
                string commandName = null;
                if (cmd.Args.Count > 1)
                    commandName = cmd.Args[1];

                if (string.IsNullOrEmpty(commandName))
                {
                    new MethodPrinter(controller).Print();
                }
                else
                {
                    var mi = controller.GetType().GetMethod(commandName);
                    if (mi != null && controller.CanExecute(mi))
                    {
                        var parameters = mi.GetParameters();
                        var inputParameters = new List<object>();
                        foreach (var parameter in parameters)
                        {
                            string sParamValue = cmd.ParamAfterSwitch(parameter.Name);
                            object paramValue = null;
                            if (!string.IsNullOrEmpty(sParamValue))
                            {
                                paramValue = Convert.ChangeType(sParamValue, parameter.ParameterType);
                            }
                            else
                            {
                                paramValue = parameter.DefaultValue;
                                if (!parameter.IsOptional)
                                {
                                    Console.WriteLine(mi.Name + GenerateMethodExample(mi));
                                    throw new Exception("Parameter not supplied: " + parameter.Name);
                                }
                            }
                            inputParameters.Add(paramValue);
                        }

                        mi.Invoke(controller, inputParameters.ToArray());
                    }
                    else
                    {
                        throw new Exception(string.Format("Command not found: {0}", commandName));
                    }
                }
            }
            catch (Exception e)
            {
                if (e.InnerException != null)
                {
                    controller.Printer.WriteError(string.Format("Fatal Error: {0}", e.InnerException.Message));
                    controller.Printer.WriteError(e.InnerException.StackTrace);                   
                }
                else
                {
                    controller.Printer.WriteError(string.Format("Fatal Error: {0}", e.Message));
                    controller.Printer.WriteError(e.StackTrace);
                }
                
            }
            Console.ForegroundColor = ConsoleColor.Gray;
        }

    }

    public interface ICommandController
    {
        ICommandLinePrinter Printer { get; }
        bool CanExecute(MethodInfo method);
    }
}
